package com.superbytecode.cloud.web.transform;

import com.superbytecode.cloud.common.context.SpringContextUtil;
import com.superbytecode.cloud.common.utils.AssertUtil;
import com.superbytecode.cloud.common.utils.FunctionalUtil;
import com.superbytecode.cloud.common.utils.VerifyUtil;
import com.superbytecode.cloud.web.transform.annotation.Transform;
import com.superbytecode.cloud.web.transform.former.Transformer;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: beacon-cloud
 * @Package: com.superbytecode.cloud.web.transform
 * @ClassName: TransformUtil
 * @Description: [转换工具类]
 * <p> </p>
 * @Author: [Lance Ting]
 * @Date: 2024/10/22 12:12
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.com Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@Slf4j
@UtilityClass
public class TransformUtil {

    private static final Map<Field, Transform> ANNOTATION_CACHE = new ConcurrentHashMap<>(1000);

    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 转换对象
     * <p>
     * 支持集合(Collection)或者单个bean, 并直接改变对象内部属性
     * </p>
     *
     * @param obj 集合或单个bean
     */
    public static void transformObj(final Object obj) {
        if (null == obj) {
            return;
        }
        log.info("待转换对象类型: {}", obj.getClass().getSimpleName());
        // 统一转成集合处理
        Collection<?> collections = (obj instanceof Collection) ? (Collection<?>) obj : Collections.singleton(obj);
        collections.forEach(bean -> {
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            // 循环处理转换字段：标注@Transform且字段类型为String
            // 只转换String类型的属性，其他类型的属性代表是嵌套情况需要过滤掉，后面处理
            Arrays.stream(declaredFields).filter(field -> matchTransformType(field, true))
                    // 循环处理
                    .forEach(field -> transformObjField(bean, field));
            // 转换嵌套字段：标注@Transform且字段类型不为String
            Arrays.stream(declaredFields).filter(field -> matchTransformType(field, false))// 过滤非String类型
                    // 递归转换
                    .forEach(field -> {
                        Object propertyValue = FunctionalUtil.sure(() -> readPropertyValue(bean, field.getName()));
                        transformObj(propertyValue);
                    });
        });
    }

    /**
     * 过滤非String类型的注解
     *
     * @param field 字段
     * @return true
     */
    private boolean matchTransformType(final Field field, boolean stringType) {
        return stringType && Objects.equals(field.getType(), String.class) && field.isAnnotationPresent(Transform.class);
    }

    /**
     * 翻译属性
     *
     * @param bean  实例
     * @param field 需要被转换的属性（标注了@Transform的）
     */
    private static void transformObjField(final Object bean, final Field field) {
        Transform transformAnnotation = loadTransformAnnotation(field);
        // 判断原字段值，如果为null则跳过转换
        String originalFieldName = loadOriginalFieldName(bean, field, transformAnnotation);
        Object originalFieldValue = FunctionalUtil.sure(() -> readPropertyValue(bean, originalFieldName));
        if (Objects.isNull(originalFieldValue)) {
            return;
        }
        // 获取转换器及转换器绑定的注解
        @SuppressWarnings("unchecked") //
        Transformer<Object, Annotation> transformer = SpringContextUtil.getBean(transformAnnotation.transformer());
        Annotation annotation = loadTransformerAnnotation(transformer, field, transformAnnotation);
        // 执行转换逻辑
        String transformValue = transformer.transform(originalFieldValue, annotation);
        // 转换结果写入当前字段
        FunctionalUtil.sure(() -> writePropertyValue(bean, originalFieldName, transformValue, String.class));
    }

    /**
     * 获取转换器绑定的注解
     * <p>如果是SimpleTransformer的子类，则绑定的注解默认都是@Transform</p>
     *
     * @param transformer 转换器接口
     * @param field       字段
     * @param annotation  注解
     * @return
     */
    @SuppressWarnings({"all"})
    private Annotation loadTransformerAnnotation(Transformer<Object, Annotation> transformer, Field field, Transform transform) {
        // Transformer上有两个泛型，第一个是转换前的值类型，第二个是是自定义注解类型
        Class<?>[] genericTypes = GenericTypeResolver.resolveTypeArguments(transform.transformer(), field.getType());
        AssertUtil.requireNonNull(genericTypes, "转换错误！实现Transform接口必须指定泛型：" + transformer.getClass().getSimpleName());
        Class<?> transformerAnnotationType = genericTypes[1];
        // 如果转换器未绑定自定义注解，此处泛型就为默认的@Transform注解
        // 否则获取字段上标注的转换器绑定自定义注解
        Annotation annotation = Objects.equals(transformerAnnotationType, Transform.class) //
                ? transform //
                : AnnotationUtils.getAnnotation(field, (Class<? extends Annotation>) transformerAnnotationType);
        verifyTransformerAnnotation(annotation, field, transformerAnnotationType);
        return annotation;
    }

    /**
     * 自定义注解检查
     *
     * @param annotation     注解
     * @param field          属性列
     * @param annotationType 注解类型
     */
    private void verifyTransformerAnnotation(Annotation annotation, Field field, Class<?> annotationType) {
        if (Objects.isNull(annotation)) return;
        String beanName = field.getDeclaringClass().getSimpleName();
        String fieldName = field.getName();
        String annotationName = annotationType.getSimpleName();
        // 如果自定义注解的方法超过一个，说明该注解有自定义功能，此时只用@Translate是错误的，应直接报错提示开发者改正
        String errmgs = String.format("property of %s have a failure annotation, miss '@%s'", fieldName, annotationName);
        AssertUtil.requirePositive(annotationType.getDeclaredMethods().length > 1, errmgs);
        // 如果自定义注解的方法只有一个（就是from），说明该自定义注解只是为了简化原注解，无新增自定义功能
        // 此时仍可以使用@Translate注解，从而在转换器升级绑定了自定义注解时还能保持向下兼容
        log.warn(MessageFormat.format("{0}类的属性{1}上的「@Transform」注解配置方式已过时，建议替换为新注解「@{2}」", beanName, fieldName, annotationName));
    }

    /**
     * 获取属性上的@Transform注解
     * <p>
     * 如果有衍生注解则合并相同属性,ps.这里实际缓存的是注解的代理对象
     * </p>
     *
     * @param field 字段
     * @return 注解
     */
    private Transform loadTransformAnnotation(Field field) {
        return ANNOTATION_CACHE.computeIfAbsent(field, k -> AnnotatedElementUtils.getMergedAnnotation(field, Transform.class));
    }

    /**
     * 获取原字段名称
     * <p>
     * 如果没有显示指定将自动推断
     * </p>
     *
     * @param bean      对象bean
     * @param field     字段
     * @param transform 注解
     * @return 原字段名称
     */
    private static String loadOriginalFieldName(Object bean, Field field, Transform transform) {
        if (!transform.from().isEmpty()) {
            return transform.from();
        }
        // 没设置from值，智能推断关联的属性名，可能为xx、xxId、xxCode
        String originalFieldName = field.getName();
        String possibleName = StringUtils.replace(originalFieldName, "Name", "");
        String possibleId = StringUtils.replace(originalFieldName, "Name", "Id");
        String possibleCode = StringUtils.replace(originalFieldName, "Name", "Code");
        List<String> fieldNames = Arrays.asList(possibleName, possibleId, possibleCode);
        // 匹配bean属性列表
        for (Field beanField : bean.getClass().getDeclaredFields()) {
            String fieldName = beanField.getName();
            if (fieldNames.contains(fieldName)) {
                // 更新注解属性值，相当于做了个缓存
                updateAnnotationProxy(transform, "from", fieldName);
                return fieldName;
            }
        }
        throw new IllegalArgumentException("转换异常：无法自动推断" + originalFieldName + "的原始字段名，请使用注解@Transform的from属性指定被转换的字段");
    }

    /**
     * 动态更新注解值，用于缓存提升性能
     * <p>注意该方法仅针对spring对注解的代理有效</p>
     *
     * @param annotation 注解
     * @param fieldName  字段名称
     * @param value      值
     */
    @SuppressWarnings("all")
    private static void updateAnnotationProxy(Annotation annotation, String fieldName, Object value) {
        InvocationHandler handler = Proxy.getInvocationHandler(annotation);
        // 代理类中有个valueCache属性，记录了原始注解的值
        Field field = FunctionalUtil.sure(() -> handler.getClass().getDeclaredField("valueCache"));
        field.setAccessible(true);
        Map<String, Object> values = (Map<String, Object>) FunctionalUtil.sure(() -> field.get(handler));
        values.put(fieldName, value);
    }

    /**
     * 读取属性值
     *
     * @param bean      对象
     * @param fieldName 属性名称
     * @return 属性值
     * @throws NoSuchMethodException     获取方法异常
     * @throws InvocationTargetException 方法调用异常
     * @throws IllegalAccessException    反射权限异常
     */
    public Object readPropertyValue(final Object bean, final String fieldName)//
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method declaredMethod = bean.getClass().getDeclaredMethod("get" + capitalize(fieldName));
        return declaredMethod.invoke(bean);
    }

    /**
     * 设值属性值
     *
     * @param bean       对象
     * @param fieldName  属性名称
     * @param fieldValue 属性值
     * @param fieldType  属性值类型
     * @param <T>        属性值类型约束
     * @throws NoSuchMethodException     获取方法异常
     * @throws InvocationTargetException 方法调用异常
     * @throws IllegalAccessException    反射权限异常
     */
    public <T> void writePropertyValue(final Object bean, final String fieldName, T fieldValue, Class<T> fieldType)//
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = bean.getClass().getDeclaredMethod("set" + capitalize(fieldName), fieldType);
        method.invoke(bean, fieldValue);
    }

    /**
     * 首字母大写
     *
     * @param fieldName 字段名称
     * @return 转换数据
     */
    private String capitalize(final String fieldName) {
        if (VerifyUtil.nonNull(fieldName)) {
            return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        }
        return fieldName;
    }
}